#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif
#ifdef __APPLE__
#include <SDL/SDL.h>
#else
#include <SDL.h>
#endif
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdarg.h>

typedef char                                    SysC8;
typedef unsigned char                           SysU8;
typedef signed char                             SysS8;
typedef unsigned short                          SysU16;
typedef signed short                            SysS16;
typedef unsigned int                            SysU32;
typedef signed int                              SysS32;
typedef float                                   SysF32;

void SysOpen(void);
void SysClose(void);
SysU32 SysUDPIP(SysC8 *IPDNSString);
SysU32 SysUDPBroadcastIP(void);
SysU32 SysUDPSend(SysU32 IPAddress,SysU16 Port,const void *Packet,SysU32 PacketByteSize);
SysU32 SysUDPBind(SysU32 IPAddress,SysU16 Port);
SysU32 SysUDPRecieve(SysU32 *IPAddress,SysU16 *Port,void *Packet,SysU32 PacketByteSize,SysU32 *BytesRecieved);
SysU32 SysMs(void);
SysS32 SysLoad(SysU32 Flags,const SysC8 *FileName,SysU32 Offset,SysU32 Size,void *Buffer);
SysS32 SysSave(SysU32 Flags,const SysC8 *FileName,SysU32 Size,void *Buffer);

#ifndef SYS_DEBUG_ODS
#define SYS_DEBUG_ODS
#endif

#ifdef SYS_DEBUG_ODS
void SysODS(const SysC8 *DebugString,...);
#define SysAssert(exp)  if (!(exp)) {SysODS( "SysAssert:%s File:%s Base File:%s Line:%d\n",#exp, __FILE__, __BASE_FILE__, __LINE__ );while(1);}
#else
inline void SysODS(const SysC8 *DebugString,...) {};
#define SysAssert(exp)
#endif

static int UDPSocket;
static SysU32 UDPBroadcastAddr=0xffffffff;

static inline unsigned int NetPort(unsigned short s)
{
    return htons(s);
}

static void SysUDPOpen(void)
{
    UDPSocket=socket(AF_INET,SOCK_DGRAM,0);
    int on=1;
    setsockopt(UDPSocket,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
    SysU32 flags = fcntl(UDPSocket, F_GETFL, 0);
    flags |= O_NONBLOCK;
    fcntl(UDPSocket, F_SETFL, flags);
    const char *ifn="en0";
    int return_val;
    int fd = -1;
    struct ifreq ifr;
    fd = socket (PF_INET, SOCK_DGRAM, 0);
    strncpy (ifr.ifr_name, ifn, sizeof(ifr.ifr_name));
    return_val = ioctl(fd,SIOCGIFBRDADDR, &ifr);
    if (return_val == 0 )
    {
        if (ifr.ifr_broadaddr.sa_family == AF_INET)
        {
            UDPBroadcastAddr =((struct sockaddr_in *)&ifr.ifr_broadaddr)->sin_addr.s_addr;
        }

    }
}

SysU32 SysUDPBroadcastIP(void)
{
    return UDPBroadcastAddr;
}

SysU32 SysUDPIP(SysC8 *IPDNSString)
{
    hostent *h;
    unsigned int ip;
    ip=inet_addr(IPDNSString);
    if(ip==INADDR_NONE)
    {
        h=gethostbyname(IPDNSString);
        if(h) ip=*(unsigned int *)h->h_addr_list[0];
    }
    return ip;
}

SysU32	SysUDPSend(SysU32 IPAddress,SysU16 Port,const void *Packet,SysU32 PacketByteSize)
{
    struct sockaddr_in a;
    a.sin_family=AF_INET;
    a.sin_addr.s_addr=IPAddress;
    a.sin_port=NetPort(Port);
    sendto(UDPSocket,(char *)Packet,PacketByteSize,0,(struct sockaddr *)&a, sizeof(a));
    return 0;
}

SysU32	SysUDPBind(SysU32 IPAddress,SysU16 Port)
{
    struct sockaddr_in a;
    int sa;
    sa=sizeof(a);
    a.sin_family=AF_INET;
    a.sin_addr.s_addr=IPAddress;
    a.sin_port=NetPort(Port);
    bind(UDPSocket,(struct sockaddr *)&a,sizeof(a));
    return 0;
}


SysU32	SysUDPRecieve(SysU32 *IPAddress,SysU16 *Port,void *Packet,SysU32 PacketByteSize,SysU32 *BytesRecieved)
{
    struct sockaddr_in a;
    socklen_t sa;
    sa=sizeof(a);
    a.sin_family=0;
    a.sin_addr.s_addr=0;
    a.sin_port=0;
    *BytesRecieved=recvfrom(UDPSocket,(void *)Packet,PacketByteSize,0,(sockaddr *)&a,&sa);
    *IPAddress=a.sin_addr.s_addr;
    *Port=NetPort(a.sin_port);
    return 0;
}

static void SysUDPClose(void)
{
}

////////////////////////////////////////////////////////MIDI/////////////////////////////////////////////////////////////////
SysC8 MidiIPName[]="127.0.0.1";
SysU32 MidiIP;
SysU32 MidiPort=9000;
void MidiOpen(void)
{
    MidiIP=SysUDPIP(MidiIPName);
    //SysU8 *IP4=(SysU8 *)&MidiIP;
    //SysODS("Midi %s IP:%d.%d.%d.%d Port:%d\n",MidiIPName,IP4[0],IP4[1],IP4[2],IP4[3],MidiPort);
}
void MidiClose(void)
{
}
void MidiSend(const SysU8 *m,SysS32 l)
{
    SysUDPSend(MidiIP,MidiPort,m,l);
    SysU8 *IP4=(SysU8 *)&MidiIP;
    return;
    SysODS("Sent To %s IP:%d.%d.%d.%d Port:%d  :\n",MidiIPName,IP4[0],IP4[1],IP4[2],IP4[3],MidiPort);
    for(SysS32 i=0; i<l; i++) SysODS("0x%02x ",m[i]);
    SysODS("\n");
}
void MidiTest(void)
{
    for(SysU32 i=0; i<5; i++)
    {
        SysU8 m[3]= {0x90,20,127};
        MidiSend(m,3);
        SysU32 l=SysMs();
        while((SysMs()-l)<1000)
        {
        }
        //SysODS("%d\n",i);
    }
}
////////////////////////////////////////////////////////MIDI/////////////////////////////////////////////////////////////////
SysU32 SysMsStart=0;

SysU32 SysMs(void)
{
    return SDL_GetTicks()-SysMsStart;
}

void SysOpen(void)
{
    SysUDPOpen();
    SysMsStart=SDL_GetTicks();
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        printf( "Unable to init SDL: %s\n", SDL_GetError() );
        exit(1);
    }
    atexit(SDL_Quit);
}

void SysClose(void)
{
    SysUDPClose();
}

SysS32  SysLoad(SysU32 Flags,const SysC8 *FileName,SysU32 Offset,SysU32 Size,void *Buffer)
{
    FILE *f;
    SysS32 l;

    f=fopen(FileName,"rb");
    if(f==NULL)
    {
        return 0;
    }
    if(Size && Buffer)
    {
        fseek(f,Offset,SEEK_SET);
        l=fread(Buffer,1,Size,f);
    }
    else
    {
        fseek(f,0,SEEK_END);
        l=ftell(f);
    }
    fclose(f);
    return l;
}

SysS32 SysSave(SysU32 Flags,const SysC8 *FileName,SysU32 Size,void *Buffer)
{
    FILE *f=fopen(FileName,"wb");
    if(Size) fwrite(Buffer,sizeof(char),Size,f);
    fclose(f);
    return 0;
}

#ifdef SYS_DEBUG_ODS
void SysODS(const SysC8 *DebugString,...)
{
    va_list args;
    va_start (args, DebugString);
    vprintf (DebugString, args);
    va_end (args);
}
#endif

SDL_Surface* SysScreen=0;
SysU8 SysKey=12,SysChannel=0;
SysF32 SysBendWheel=0,SysBendButton=0;
SysS32 SysLastBend=0,SysLastMod=0,SysVelocity=0x7f;

struct CFGS
{
    const SysC8 *Layout[2];
    SysS32 Offset[2];
    SysS32 BendSemitones;
    SysS32 Vel;
    SysF32 XS,YS;
}
Cfg=
{
    {
        "(\\azsxcfvgbhnmk,l./')#",
        "T1q2we4r5t6yu8i9op-[=]BE"
    },
    {0,12+1},
    2,
    32,
    128,128
};

SysS32 KeyOffset(SysC8 c)
{
    for(SysU32 j=0; j<2; j++)
    {
        SysU32 i=0;
        const SysC8 *s=Cfg.Layout[j];
        SysAssert(s);
        while(s[i])
        {
            if(c==s[i])return (i+Cfg.Offset[j]);
            i++;
        }
    }
    return -1;
}
SysS32 SysNoteOfKey(SysS32 c)
{
    if(c==SDLK_LEFT) c='L';
    if(c==SDLK_RIGHT) c='R';
    if(c==SDLK_UP) c='U';
    if(c==SDLK_DOWN) c='D';
    if(c==SDLK_TAB) c='T';
    if(c==SDLK_BACKSPACE) c='B';
    if(c==SDLK_RETURN) c='E';
    if(c==SDLK_LSHIFT) c='(';
    if(c==SDLK_RSHIFT) c=')';
    if(c==SDLK_LALT) c='{';
    if(c==SDLK_RALT) c='}';
    if(c==SDLK_LCTRL) c='<';
    if(c==SDLK_RCTRL) c='>';
    if(c==SDLK_SPACE) c='S';
    if(c>127) return -1;
    if(c<32) return -1;
    SysS32 n=KeyOffset(c);
    if(n<0) return -1;
    n+=SysKey;
    return n&127;
}

SysS32 NotesOn=0;
SysS32 SysUpdate(void)
{
    static SysS32 Shift=0;
    SysAssert(Shift>=0);
    SysS32 f=0;
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
        switch (event.type)
        {
        case SDL_QUIT:
            f|=1;
            break;
        case SDL_MOUSEBUTTONDOWN:
            switch(event.button.button)
            {
            case SDL_BUTTON_WHEELUP:
                SysVelocity+=Cfg.Vel;
                break;
            case SDL_BUTTON_WHEELDOWN:
                SysVelocity-=Cfg.Vel;
                break;
            }
            break;
        case SDL_KEYDOWN:
        {
            SysS32 n=SysNoteOfKey(event.key.keysym.sym);
            if(n>=0)
            {
                SysU8 m[3]= {0x90|SysChannel,n,SysVelocity};
                MidiSend(m,3);
                SysBendWheel=0;
                NotesOn++;
                SysODS("%d:%d\n",NotesOn,n);
            }
            switch(event.key.keysym.sym)
            {
            case SDLK_LALT:
            case SDLK_RALT:
                Shift++;
                break;
            case SDLK_F12:
                if(Shift) SysKey+=12;
                else SysKey-=12;
                break;
            case SDLK_F11:
                if(Shift) SysKey++;
                else SysKey--;
                break;
            case SDLK_F10:
                if(Shift) SysChannel++;
                else SysChannel--;
                break;
            default:
                break;
            }
            SysKey&=127;
            SysChannel&=0xf;
            if(n<0)SysODS("Channel:%2d Key:(Midi:%d 0x%02x) Chromatic Octave:%d Note(0-11):%d\n",SysChannel,SysKey,SysKey,SysKey/12,SysKey%12);
        }
        break;
        case SDL_KEYUP:
        {
            switch(event.key.keysym.sym)
            {
            case SDLK_LALT:
            case SDLK_RALT:
                Shift--;
                break;
            default:
                break;
            }
            SysS32 n=SysNoteOfKey(event.key.keysym.sym);
            if(n>=0)
            {
                SysU8 m[3]= {0x80|SysChannel,n,SysVelocity};
                MidiSend(m,3);
                NotesOn--;
                SysAssert(NotesOn>=0);
            }
        }
        break;
        }
    }
    if(SysVelocity<0) SysVelocity=0;
    if(SysVelocity>0x7f) SysVelocity=0x7f;
    static SysS32 lx=0,ly=0,LastNotesOn=0;
    SysS32 x,y;
    SysS32 BBend=SDL_GetMouseState(&x,&y);
    SysS32 Bend=0x2000;
    if(NotesOn!=LastNotesOn)
    {
        if(!LastNotesOn)
        {
            lx=x;
            ly=y;
        }
        LastNotesOn=NotesOn;
    }
    if(BBend&5)
    {
        SysS32 b=0;
        if(BBend&1) b-=1;
        if(BBend&4) b+=1;
        Bend=0x2000+(b*0x1fff);
    }
    SysF32 dy=-(y-ly)/Cfg.YS;
    if(dy>1) dy=1;
    else if(dy<-1) dy=-1;
    Bend=Bend+dy*0x1fff;
    if(Bend<0) Bend=0;
    if(Bend>0x3fff) Bend=0x3fff;
    if(Bend!=SysLastBend)
    {
        SysU8 m[3]= {0xe0|SysChannel,Bend&0x7f,(Bend>>7)&0x7f};
        MidiSend(m,3);
        SysLastBend=Bend;
    }

    SysF32 dx=(x-lx)/Cfg.XS;
    if(dx>1) dx=1;
    else if(dx<-1) dx=-1;
    SysS32 Mod=dx*0x7f;
    if(Mod!=SysLastMod)
    {
        SysU8 m[3]= {0xb0|SysChannel,1,Mod};
        MidiSend(m,3);
        SysLastMod=Mod;
    }
    return f;
}


SysS32 SysUserFrame(SysS32 Flags);
int main ( int argc, char** argv )
{
    SysOpen();

    SysScreen = SDL_SetVideoMode(512,512,16,SDL_HWSURFACE|SDL_DOUBLEBUF);
    SysAssert(SysScreen);
    while (!SysUpdate())
    {
        if(SysUserFrame(0)) break;
        SDL_Flip(SysScreen);
    }
    SysClose();
    return 0;
}


SysS32 SysUserFrame(SysS32 Flags)
{
    static SysU32 Mode=0;
    static SDL_Surface* bmp=0;

    switch(Mode)
    {
    case 0:
        MidiOpen();
        bmp = SDL_LoadBMP("MoKeMi.bmp");
        SysAssert(bmp);
        Mode++;
        break;
    case 1:
    {
        SDL_Rect dstrect;
        dstrect.x = (SysScreen->w - bmp->w) / 2;
        dstrect.y = (SysScreen->h - bmp->h) / 2;
        SDL_FillRect(SysScreen, 0, SDL_MapRGB(SysScreen->format, 0, 0, 0));
        SDL_BlitSurface(bmp, 0, SysScreen, &dstrect);

    }
    break;
    case 2:
        SDL_FreeSurface(bmp);
        MidiClose();
        return 1;
        break;

    }
    return 0;
}
